package com.gitee.sparta.core.property;

import cn.hutool.core.util.StrUtil;
import com.gitee.sparta.common.SpartaConstant;
import com.gitee.sparta.common.SpartaErrorEnum;
import com.gitee.sparta.common.exception.Assert;
import com.gitee.sparta.common.exception.SpartaException;
import lombok.Getter;
import lombok.Setter;
import org.redisson.codec.JsonJacksonCodec;
import org.redisson.config.*;
import org.springframework.beans.BeanUtils;
import org.springframework.boot.context.properties.ConfigurationProperties;

import javax.annotation.PostConstruct;
import java.time.Duration;
import java.util.Objects;

/**
 * sparta 属性配置，提供给 springboot application.properties 的配置项
 *
 * @author zyred
 * @since v 0.0.1
 */
@Getter
@Setter
@ConfigurationProperties(prefix = "spring.sparta")
public class SpartaCoreProperties {

    private final static String DEFAULT_URL = "redis://localhost:6379";
    private final static String INVALID_FILE_PATH = "Invalid file path";

    /** 监听器线程池配置 **/
    private ThreadsProperties threads = new ThreadsProperties();
    /** 项目名称，为了更好的区分所被消费的队列 **/
    private String systemName = "sparta";
    /** 针对 redisson 的配置信息 **/
    private RedissonProperties redisson = new RedissonProperties();
    /** redisson config 对象 **/
    private final Config config = new Config();
    /** 从待消费队列中消费，最大等待时间 **/
    private Duration consumeWaitTime = Duration.ZERO;
    /** 重试次数 **/
    private int retry = 3;
    /** 续期次数 */
    private int renew = 3;
    /** 隔离机制：分布式情况下，一套代码被部署多台机器会导致 */
    private String isolation;
    /**
     * 是否启用单机宕机处理，默认开启
     * <p>
     * 宕机策略是为了保证数据不丢失的解决方案，详情请阅读相关文档
     */
    private boolean enableCloseRollback = true;
    /**
     * 是否启用广播，默认关闭广播功能 false
     * <p>
     * 广播功能是内部提供的一个机制，并非使用的 redisson 的发布订阅模式
     * 广播的使用方式，请阅读相关文档
     */
    private boolean enableBroadcast = false;
    /**
     * 是否开启自动 ack 功能，默认开启 true
     * <p>
     * 如果关闭了自动 ack 在包含 @SpartaListener 的方法的
     * 入参必须接收 Channel 对象，通过 Channel#ack() 确认消息，
     * 否则视为未 ack 的消息
     */
    private boolean enableAutoAck = true;
    /**
     * 未 ack 的消息持久化处理, 默认不处理未 ack 的消息 false
     * 开启未 ack 的消息，将会把这些消息存入到 redis 的 denys_tab 中
     * 在 redis 中存储的数据不是永久的，需要注意
     * <p>
     * 存储到 redis 中未 ack 的数据处理，提供了相应的 api，请阅读相关文档
     */
    private boolean enableDenyPersistence = false;
    /**
     * 是否开启元数据的压缩，默认开启， false 关闭
     * 什么是元数据：
     *  则是用户的 Json 数据，在 metadata_tab 中的 Json 数据
     * 为什么要压缩：
     *  则是减少网络传输的占用
     * 建议： 开发、测试阶段关闭元数据压缩，数据不直观。上线开启元数据压缩
     */
    private boolean enableMetadataCompress = false;

    /**
     * true：单机模式启动
     * false：集群模式启动
     */
    private boolean standalone = false;

    /**
     * 集群情况下 leader 是否专注与数据的转发，不参与数据的消费
     * 默认 leader 是需要参与消费的，关闭后 leader 将不再消费数据
     */
    private boolean focusCarry = true;

    /**
     * 集群配置 stand-alone
     */
    private ClusterProperties cluster = new ClusterProperties();

    public void setSystemName(String systemName) {
        Assert.isTrue(StrUtil.isBlank(systemName), SpartaErrorEnum.RETRY_COUNT_VALID);
        this.systemName = systemName;
    }

    public void setConsumeWaitTime(Duration consumeWaitTime) {
        if (consumeWaitTime == null) {
            return;
        }
        this.consumeWaitTime = consumeWaitTime;
    }

    public void setRetry(int retry) {
        Assert.isTrue(retry <= 0 || retry > 20, SpartaErrorEnum.RETRY_COUNT_VALUE_SCOPE);
        this.retry = (retry - 1);
    }

    public void setRenew(int renew) {
        Assert.isTrue(renew <= 0 || renew > 30, SpartaErrorEnum.RENEW_COUNT_VALUE_SCOPE);
        this.renew = renew;
    }

    @PostConstruct
    public void initConfig() {
        if (Objects.isNull(redisson)) {
            throw new SpartaException(SpartaErrorEnum.NOT_FOUND_REDISSON_CONFIG);
        }

        if (Objects.nonNull(redisson.getClusterServersConfig())) {
            ClusterServersConfig target = config.useClusterServers();
            BeanUtils.copyProperties(redisson.getClusterServersConfig(), target);
        }

        else if (Objects.nonNull(redisson.getSentinelServersConfig())) {
            SentinelServersConfig target = config.useSentinelServers();
            BeanUtils.copyProperties(redisson.getSentinelServersConfig(), target);
        }

        else if (Objects.nonNull(redisson.getMasterSlaveServersConfig())) {
            MasterSlaveServersConfig target = config.useMasterSlaveServers();
            BeanUtils.copyProperties(redisson.getMasterSlaveServersConfig(), target);
        }

        else {
            SingleServerConfig target = config.useSingleServer();
            SingleServerConfig singleServerConfig;
            if (Objects.isNull(this.redisson)) {
                singleServerConfig = new Config().useSingleServer().setAddress(DEFAULT_URL).setDatabase(6);
                redisson.setSingleServerConfig(singleServerConfig);
            } else {
                singleServerConfig = redisson.getSingleServerConfig();
            }
            BeanUtils.copyProperties(singleServerConfig, target);
        }
        this.config.setCodec(new JsonJacksonCodec());
    }


    public String getSystemName() {
        return SpartaConstant.LEFT_BRACKETS.concat(this.systemName).concat(SpartaConstant.RIGHT_BRACKETS);
    }

    public String getServerTable() {
        return getSystemName().concat(SpartaConstant.COLON).concat(SpartaConstant.SERVICE_TABLE);
    }

    public String getShaTable() {
        return getSystemName().concat(SpartaConstant.COLON).concat(SpartaConstant.SHA_TABLE);
    }


    public String getLock () {
        return this.getSystemName().concat(SpartaConstant.COLON).concat(SpartaConstant.REGEDIT_LOCK);
    }

    public String getDemotionLock () {
        return this.getSystemName().concat(SpartaConstant.COLON).concat(SpartaConstant.DEMOTION_FOLLOWER_LOCK);
    }
}
